package com.hjk.htimer;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by dengdai68 on 17/3/21.
 */
public final class HTimer {

    private static AtomicLong taskSeed = new AtomicLong(0);
    private int currentIndex = -1;
    private AtomicInteger pending = new AtomicInteger(0);


    private boolean start = false;
    private boolean stop = false;

    private Lock lock;
    private Condition empty;

    private AtomicReferenceArray<TimerTask> ringbuffer;
    private ExecutorService executorService;

    public static HTimer createHTimer(int ringSize,int threadSize){
        return createHTimer(ringSize, Executors.newFixedThreadPool(threadSize));
    }

    public static HTimer createHTimer(int ringSize, ExecutorService executorService){
        return new HTimer(ringSize,executorService);
    }

    public HTimer(int ringSize, ExecutorService executorService) {
        if (ringSize < 1) {
            throw new IllegalArgumentException("ringSize must not be less than 1");
        }
        if (Integer.bitCount(ringSize) != 1) {
            throw new IllegalArgumentException("ringSize must be a power of 2");
        }
        ringbuffer = new AtomicReferenceArray(ringSize);
        this.executorService = executorService;
        this.lock = new ReentrantLock(true);
        empty = lock.newCondition();
    }

    public void start(){
        start = true;
        taskRun();
        taskTimer();
    }

    public void schedule(TimerTask task, int delay) {
        schedule(task,delay,-1);
    }

    public void schedule(TimerTask task, int delay, int period) {
        task.period = period;
        pushTask(task,delay);
    }

    public void pushTask(TimerTask task, int delay){
        task.circle = clacBufferCircle(delay);
        TimerTask old;
        int ind;
        do {
            ind = clacBufferIndex(currentIndex,delay);
            old = ringbuffer.get(ind);
        } while (!ringbuffer.compareAndSet(ind,old,task));
        task.next = old;
        if (old != null){
            old.prev = task;
        }
        task.prev = null;
    }



    public void removeTask(TimerTask task,int ind){
        boolean first = false;
        if (ringbuffer.get(ind) == task){
            first = true;
            if(!ringbuffer.compareAndSet(ind,task,task.next)){
                first = false;
            } else {
                if (task.next != null){
                    task.next.prev = null;
                }
            }
        }
        if (!first){
            task.prev.next = task.next;
            if (task.next != null){
                task.next.prev = task.prev;
            }
        }
        task.next = null;
        task.prev = null;
    }

    private int clacBufferIndex(int currIndex ,int delay) {
        return ((currIndex + delay)&(ringbuffer.length()-1));
    }
    private int clacBufferCircle(int delay) {
        if ((delay&(ringbuffer.length()-1)) == 0){
            return delay/ringbuffer.length() - 1;
        }
        return delay/ringbuffer.length();
    }

    public void stop(){
        start = false;
        stop = true;
    }

    private void incrementcanCurrentIndex(){
        currentIndex = increment(currentIndex);
    }

    private int increment(int ind){
        if (ind + 1 == ringbuffer.length()){
            ind = 0;
        } else {
            ind++;
        }
        return ind;
    }

    private void taskTimer(){
        new Thread(()-> {
            long prev = System.currentTimeMillis();
            long next;
            while (start){
                try {
                    lock.lockInterruptibly();
                    pending.incrementAndGet();
                    empty.signal();
                } catch (InterruptedException e) {
                } finally {
                    lock.unlock();
                }
                next = 1000 + prev;
                try {
                    Thread.sleep(next - System.currentTimeMillis());
                } catch (InterruptedException e) {
                }
                prev = next;
            }
        }).start();
    }

    private void taskRun(){
        new Thread(()->{
            while (!stop){
                try {
                    lock.lockInterruptibly();
                    if (pending.get() == 0){
                        empty.await();
                    }
                } catch (Exception e){
                } finally {
                    lock.unlock();
                }
                try {
                    TimerTask curr = ringbuffer.get(increment(currentIndex));
                    incrementcanCurrentIndex(); // 放在这里很重要！！！
                    while (curr != null ){
                        TimerTask currTemp = curr;
                        curr = curr.next;
                        if (currTemp.circle <= 0) {
                            removeTask(currTemp,currentIndex);
                            if (currTemp.isperiodic())
                                pushTask(currTemp,currTemp.period);
                            executorService.submit(currTemp);
                        } else {
                            currTemp.circle = currTemp.circle -1;
                        }
                    }
                    pending.decrementAndGet();
                } catch (Exception e) {
                }
            }
        }).start();
    }

    public abstract class TimerTask implements Runnable{
        private long taskserial;

        private int period; // -1 代表不循环
        private int circle; // 处于ringbuffer 中的第几圈

        private TimerTask next;
        private TimerTask prev;

        public boolean isperiodic(){
            return period != -1;
        }

        public TimerTask() {
            taskserial = taskSeed.incrementAndGet();
        }

        public void run(){
            try {
                work();
            } catch (Throwable t){
            }
        }
        public abstract void work();
    }
}
